#!/usr/bin/env bash

include "objectv5.sh"
include "regex.sh"
include "test.sh"

ObjectV5::DefineClass ArgumentsV3 "
    --integer=index,-1
    --string=current
    --array=args
    --array=skipped
"

function ArgumentsV3::ArgumentsV3()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    while (( $# > 0 )); do
        ArgumentsV3::args "$this" push "$1"; shift
    done

    if (( "$(ArgumentsV3::index "$this")" + 1 < "$(ArgumentsV3::args "$this" len)" )); then
        ArgumentsV3::GoNext "$this"
    else
        ArgumentsV3::index "$this" inc
    fi
    return
}

function ArgumentsV3::IsEnd()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    (( "$(ArgumentsV3::index "$this")" >= "$(ArgumentsV3::args "$this" len)" ))
    return
}

function ArgumentsV3::GoNext()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    if ArgumentsV3::IsEnd "$this"; then
        assert_fail "没有更多参数，不能GoNext了"
        return 1
    fi

    ArgumentsV3::index "$this" inc
    local index="$(ArgumentsV3::index "$this")"
    ArgumentsV3::current "$this" "$(ArgumentsV3::args "$this" "$index")"
    return
}

function ArgumentsV3::GoSkip()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    ArgumentsV3::skipped "$this" push "$(ArgumentsV3::Get "$this")"
    ArgumentsV3::GoNext "$this"
    return
}

function ArgumentsV3::GoSkipAll()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    while ! ArgumentsV3::IsEnd "$this"; do
        ArgumentsV3::GoSkip "$this"
    done
    return
}

function ArgumentsV3::Get()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    ArgumentsV3::current "$this"
    return
}

function ArgumentsV3::IsKVOption()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    [[ "$(ArgumentsV3::Get "$this")" =~ (^--${regex_id2}=) ]]
    return
}

function ArgumentsV3::IsOption()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    [[ "$(ArgumentsV3::Get "$this")" =~ (^-) ]]
    return
}

function ArgumentsV3::GetOption()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    if ! ArgumentsV3::IsOption "$this"; then
        assert_fail "GetOption需要当前值为选项，当前值: '$(ArgumentsV3::Get "$this")'"
        return 1
    fi

    local option="$(ArgumentsV3::Get "$this")"
    while [ "${option:0:1}" == "-" ]; do
        option="${option:1}"
    done
    echo "$option"
    return
}

function ArgumentsV3::GetKey()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    if ! ArgumentsV3::IsKVOption "$this"; then
        assert_fail "GetKey需要当前值为Key-Value选项，当前值: '$(ArgumentsV3::Get "$this")'"
        return 1
    fi

    local option="$(ArgumentsV3::Get "$this")"
    option="${option:2}"
    echo "${option%%=*}"
    return
}

function ArgumentsV3::GetVal()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    if ! ArgumentsV3::IsKVOption "$this"; then
        assert_fail "GetVal需要当前值为Key-Value选项，当前值: '$(ArgumentsV3::Get "$this")'"
        return 1
    fi

    local option="$(ArgumentsV3::Get "$this")"
    echo "${option#*=}"
    return
}

function ArgumentsV3::GetSkipped()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    ArgumentsV3::skipped "$this" all
    return
}

function ArgumentsV3::GetRest()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local i
    for (( i = $(ArgumentsV3::index "$this"); i < $(ArgumentsV3::args "$this" len); i++ )); do
        echo -n "$(ArgumentsV3::args "$this" "$i") "
    done
    return
}

function ArgumentsV3::ProcessOption()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local null_args_mode="$1"; shift
    local not_option_mode="$1"; shift
    local option_func_prefix="$1"; shift

    if (( $(ArgumentsV3::index "$this") > 0 )); then
        assert_fail "调用ProcessOption之前已被解析过了"
        return 1
    fi

    if ArgumentsV3::IsEnd "$this"; then
        debuglog "null_args_mode: $null_args_mode"
        case "$null_args_mode" in
            null) ;;
            help) ArgumentsV3::args "$this" push "--help"
                  ArgumentsV3::current "$this" "--help" ;;
            *) assert_fail "不认识的无参操作模式: $null_args_mode"
               return 1 ;;
        esac
    fi

    while ! ArgumentsV3::IsEnd "$this"; do
        if ! ArgumentsV3::IsOption "$this"; then
            case "$not_option_mode" in
                skip) debuglog "Skip $(ArgumentsV3::Get "$this")"
                      ArgumentsV3::GoSkip "$this"
                      continue ;;
                break) debuglog "SkipAll $(ArgumentsV3::GetRest "$this")"
                       ArgumentsV3::GoSkipAll "$this"
                       break ;;
                *) assert_fail "不认识的非选项操作模式: $not_option_mode"
                   return 1 ;;
            esac
        fi

        if ArgumentsV3::IsKVOption "$this"; then
            local option="--$(ArgumentsV3::GetKey "$this")"
            local func="${option_func_prefix}${option}"
            if ! declare -f $func 1>/dev/null; then
                loge "选项${option}的处理函数不存在"
            else
                debuglog "process kvoption: $func"
                $func "$this"
            fi
            ArgumentsV3::GoNext "$this"
        else
            local option="$(ArgumentsV3::Get "$this")"
            ArgumentsV3::GoNext "$this"
            local func="${option_func_prefix}${option}"
            if ! declare -f $func 1>/dev/null; then
                loge "选项${option}的处理函数不存在"
            else
                debuglog "process option: $func"
                $func "$this"
            fi
        fi
    done
    return
}

function ArgumentsV3::UseSkipped()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    if ! ArgumentsV3::IsPtr "$this"; then
        assert_fail "UseSkipped cannot called by innerobj"
        return 1
    fi

    local skipped=($(ArgumentsV3::GetSkipped "$this"))
    ArgumentsV3::Del "$this"
    ArgumentsV3::New "$this" "${skipped[@]}"
    return
}

function ArgumentsV3::Test1()
{
    ArgumentsV3 args -c --output a --index 2 --success --filter="123" normal

    # -c
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::should_return_false ArgumentsV3::IsKVOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be -c
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be c
    ArgumentsV3::GoNext args

    # --output a
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::should_return_false ArgumentsV3::IsKVOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be --output
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be output
    ArgumentsV3::GoNext args

    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_false ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be a
    ArgumentsV3::GoNext args

    # --index 2
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::should_return_false ArgumentsV3::IsKVOption args
    ArgumentsV3::GoSkip args

    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_false ArgumentsV3::IsOption args
    ArgumentsV3::GoSkip args

    test::run_cmd ArgumentsV3::GetSkipped args
    test::should_be "--index 2"
    skipped=($(ArgumentsV3::GetSkipped args))
    test::run_cmd echo ${skipped[0]}
    test::should_be --index

    # --success
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::should_return_false ArgumentsV3::IsKVOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be --success
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be success
    ArgumentsV3::GoNext args

    # --filter="123"
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::should_return_true ArgumentsV3::IsKVOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be --filter="123"
    test::run_cmd ArgumentsV3::GetKey args
    test::should_be filter
    test::run_cmd ArgumentsV3::GetVal args
    test::should_be 123
    ArgumentsV3::GoNext args

    # normal
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_false ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be normal
    ArgumentsV3::GoNext args

    test::should_return_true ArgumentsV3::IsEnd args
}
test::register_test ArgumentsV3::Test1

function ArgumentsV3::Test2()
{
    ArgumentsV3 args -c 1

        ArgumentsV3 args2 -a 2

        # -a
        test::should_return_false ArgumentsV3::IsEnd args2
        test::should_return_true ArgumentsV3::IsOption args2
        test::run_cmd ArgumentsV3::Get args2
        test::should_be -a
        test::run_cmd ArgumentsV3::GetOption args2
        test::should_be a
        ArgumentsV3::GoNext args2

    # -c
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be -c
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be c
    ArgumentsV3::GoNext args

        test::should_return_false ArgumentsV3::IsEnd args2
        test::should_return_false ArgumentsV3::IsOption args2
        test::run_cmd ArgumentsV3::Get args2
        test::should_be 2
        ArgumentsV3::GoNext args2

        test::should_return_true ArgumentsV3::IsEnd args2

    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_false ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be 1
    ArgumentsV3::GoNext args

    test::should_return_true ArgumentsV3::IsEnd args
    return
}
test::register_test ArgumentsV3::Test2

function ArgumentsV3::Test3()
{
    ArgumentsV3 args - --

    # -
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be -
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be ""
    ArgumentsV3::GoNext args

    # -
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_true ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be --
    test::run_cmd ArgumentsV3::GetOption args
    test::should_be ""
    ArgumentsV3::GoNext args

    test::should_return_true ArgumentsV3::IsEnd args
    return
}
test::register_test ArgumentsV3::Test3

function ArgumentsV3::Test4()
{
    ArgumentsV3 args 1 2 3

    # 1
    test::should_return_false ArgumentsV3::IsEnd args
    test::should_return_false ArgumentsV3::IsOption args
    test::run_cmd ArgumentsV3::Get args
    test::should_be "1"
    ArgumentsV3::GoNext args

    test::run_cmd ArgumentsV3::GetRest args
    test::should_be '2 3 '
    return
}
test::register_test ArgumentsV3::Test4

function ArgumentsV3::Test5()
{
    ArgumentsV3::New args 1 2 3
    ArgumentsV3::GoNext args # 1
    ArgumentsV3::GoSkip args # 2
    ArgumentsV3::GoSkip args # 3
    ArgumentsV3::UseSkipped args

    # 2
    test::run_cmd ArgumentsV3::Get args
    test::should_be "2"
    ArgumentsV3::GoNext args

    # 3
    test::run_cmd ArgumentsV3::Get args
    test::should_be "3"
    ArgumentsV3::GoNext args
    ArgumentsV3::Del args
    return
}
test::register_test ArgumentsV3::Test5

function ArgumentsV3::Test6()
{
    local response_a=""
    local response_b=""
    local response_c=""
    local response_d=""
    local response_e=""
    function ArgumentsV3::Test6::-a()
    {
        response_a="${!1}"
    }

    function ArgumentsV3::Test6::--b()
    {
        [ $# == 0 ] && assert_fail "this"
        local this="$1"; shift
        response_b="$(ArgumentsV3::GetVal "$this")"
    }

    function ArgumentsV3::Test6::-c()
    {
        [ $# == 0 ] && assert_fail "this"
        local this="$1"; shift
        response_c="$(ArgumentsV3::Get "$this")"
        ArgumentsV3::GoNext "$this"
    }

    function ArgumentsV3::Test6::--d()
    {
        [ $# == 0 ] && assert_fail "this"
        local this="$1"; shift
        response_d="$(ArgumentsV3::Get "$this")"
        ArgumentsV3::GoNext "$this"
    }

    function ArgumentsV3::Test6::--e()
    {
        [ $# == 0 ] && assert_fail "this"
        local this="$1"; shift
        response_e="$(ArgumentsV3::GetRest "$this")"
        ArgumentsV3::GoNext "$this"
    }

    ArgumentsV3 args "-a" "--b=1" "-c" 2 "--d" 3 "--e" 4 5 6
    ArgumentsV3::ProcessOption args "null" "break" "ArgumentsV3::Test6::"
    test::var_should_be response_a "$args"
    test::var_should_be response_b 1
    test::var_should_be response_c 2
    test::var_should_be response_d 3
    test::var_should_be response_e "4 5 6 "
    test::run_cmd ArgumentsV3::GetSkipped args
    test::should_be "5 6"
}
test::register_test ArgumentsV3::Test6

function ArgumentsV3::Test7()
{
    local response_help
    function ArgumentsV3::Test7::--help()
    {
        response_help=ok
    }

    ArgumentsV3 args
    ArgumentsV3::ProcessOption args "help" "break" "ArgumentsV3::Test7::"
    test::var_should_be response_help ok
}
test::register_test ArgumentsV3::Test7
